package com.study.socket.service;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.study.socket.exception.NonExistingClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author yuhuan
 * @date 2023/11/21
 */
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {

    /**
     * 静态变量，用来记录当前在线连接数，线程安全的类。
     */
    private static final AtomicInteger onlineSessionClientCount = new AtomicInteger(0);

    /**
     * 用来存放每个在线客户端对应的会话
     */
    private static final Map<String, Session> onlineSessionClientMap = new ConcurrentHashMap<>();

    /**
     * 注入的Bean一定要是静态的，否则会无法注入
     */
    private static BeanRegister register;

    /**
     * TODO 这里切记不要使用构造函数去注入Bean，否则建立连接时会抛出{@code Endpoint instance creation failed}
     */
    @Autowired
    public void setRegister(BeanRegister register) {
        WebSocketServer.register = register;
    }

    /**
     * 连接建立成功调用的方法
     *
     * @param sid     每次页面建立连接时传入到服务端的id，比如用户id、token等
     * @param session 与某个客户端的连接会话，需要通过它来给客户端发送消息
     */
    @OnOpen
    public void onOpen(@PathParam("sid") String sid, Session session) {
        // 将页面的sid和session绑定
        onlineSessionClientMap.put(sid, session);
        // 在线数量加1
        onlineSessionClientCount.incrementAndGet();
        // 测试Bean的注入功能
        if (ObjectUtils.isEmpty(register)) {
            sendToOne(sid, "Bean注入失败");
        } else {
            sendToOne(sid, "加载" + register.getName());
        }
        sendToOne(sid, "连接成功");
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid, Session session) {
        // 移除在线的客户端
        onlineSessionClientMap.remove(sid);
        // 在线数量减1
        onlineSessionClientCount.decrementAndGet();
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 界面传递来的数据，一般为JSON格式
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        JSONObject jsonObject = JSON.parseObject(message);
        String cid = jsonObject.getString("cid");
        String sid = jsonObject.getString("sid");
        String msg = jsonObject.getString("message");
        // 模拟约定：如果未指定sid信息，则群发，否则就单独发送
        if (sid == null || sid.length() == 0) {
            sendToAll(cid, msg);
        } else {
            sendToOne(sid, msg);
        }
    }

    /**
     * 发生错误时调用的方法
     */
    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    /**
     * 单发消息（对点）
     */
    public void sendToOne(String sid, String message) {
        Session toSession = onlineSessionClientMap.get(sid);
        if (toSession == null) {
            throw new NonExistingClient("不存在的客户端!");
        }
        // 同步发送消息，可选择异步发送
        try {
            toSession.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 群发消息（广播），排除自身客户端
     *
     * @param selfId 发消息的客户自身ID
     */
    public void sendToAll(String selfId, String message) {
        // 遍历在线客户端
        onlineSessionClientMap.forEach((onlineSid, toSession) -> {
            // 排除掉自己
            if (!selfId.equalsIgnoreCase(onlineSid)) {
                // 异步发送消息
                toSession.getAsyncRemote().sendText(message);
            }
        });
    }

    /**
     * 广播通知，通知所有客户端
     */
    public static void broadcast(String message) {
        onlineSessionClientMap.forEach((onlineSid, toSession) -> {
            toSession.getAsyncRemote().sendText(message);
        });
    }

    /**
     * 广播通知，通知指定客户端
     */
    public static void broadcast(String message, List<String> users) {
        for (String user : users) {
            Session toSession = onlineSessionClientMap.get(user);
            if (toSession == null) {
                continue;
            }
            toSession.getAsyncRemote().sendText(message);
        }
    }
}
